package com.sh.cloudrender.application.service;

import com.alibaba.druid.support.json.JSONUtils;
import com.sh.cloudrender.application.dto.engine.EngineDTO;
import com.sh.cloudrender.application.dto.engine.EngineSpec;
import com.sh.cloudrender.common.constants.EngineStateEnum;
import com.sh.cloudrender.common.utils.SnowFlakeFactory;
import com.sh.cloudrender.domian.service.kube.*;
import com.sh.cloudrender.infrastructure.repository.EngineRepo;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

@Slf4j
@Service
public class EngineServiceImpl implements EngineService {

    private final String CONSTANT_PREFIX = "sh-engine-";

    @Value("${engine.namespace}")
    private String namespace;
    @Autowired
    private DeployService deployService;
    @Autowired
    private SvcService svcService;
    @Autowired
    private PodService podService;
    @Autowired
    private TraefikService traefikService;
    @Autowired
    private NodeService nodeService;
    @Autowired
    private EngineRepo engineRepo;


    @Override
    public synchronized EngineDTO apply(String userId, String engineName, EngineSpec spec, String image) {
        if (spec == null) {
            spec = new EngineSpec();
        }

        if (!nodeService.checkResourceAvailable(spec.getCpus(), spec.getMemory(), spec.getEnableGpu() == 0)) {
            log.warn("resource is not sufficient");
            throw new RuntimeException("resource is not sufficient");
        }
        long engineIdPart = SnowFlakeFactory.get().nextId();
        //生成引擎id
        String engineId = CONSTANT_PREFIX + engineIdPart;
        V1Pod v1Pod = podService.getOne(engineId, namespace);
        if (v1Pod == null) {
            if (!deployService.exist(namespace, engineId)) {
                if( 0  == spec.getContainerPort() ){
                    spec.setContainerPort(3000);
                }
                V1Deployment deployment = deployService.createOrUpdateDeployment(engineId, namespace, image,
                        1, String.valueOf(spec.getCpus()), String.valueOf(spec.getMemory()), spec.getEnableGpu() == 1,spec.getContainerPort());
                if (deployment == null) {
                    log.error("userId:{} apply engine failed! reason: create deployment error! engine:{}", userId, engineId);
                    return null;
                }
            } else if (!deployService.scaleDeploymentPods(engineId, namespace, 1)) {
                log.error("userId:{} apply engine failed! reason: scale pod error! engineId:{}", userId, engineId);
                return null;
            }
        }

        V1Service svc = svcService.getSvc(engineId, namespace);
        if (svc == null) {
            svc = svcService.createSvc(engineId, namespace,spec.getContainerPort());
            if (svc == null) {
                log.error("userId:{} apply engine failed! reason: create svc error! engineId:{}", userId, engineId);
                return null;
            }
        }

        int retryCount = 3;
        while (v1Pod == null && retryCount > 0) {
            v1Pod = podService.getOne(engineId, namespace);
            LockSupport.parkNanos(1000000 * 100);
            retryCount--;
        }

        if (v1Pod == null) {
            log.error("userId:{} apply engine failed! load pod error! engineId:{}", userId, engineId);
            return null;
        }

        String prefix = "/" + engineId.replace("-", "/");
        Integer appPort = svc.getSpec().getPorts().get(0).getPort();
        if (!traefikService.createIngressRoute(engineId, namespace, prefix, engineId, appPort)) {
            log.error("userId:{} apply engine failed! reason: create ingress route failed! engineId:{}", userId, engineId);
            return null;
        }


        EngineDTO result = new EngineDTO();
        result.setEngineId(engineId);
        result.setCreateTime(new Date());
        try {
            result.setState(EngineStateEnum.getByDesc(v1Pod.getStatus().getPhase().toLowerCase()).getCode());
            if (null == v1Pod.getStatus().getStartTime()) {
                v1Pod.getStatus().setStartTime(OffsetDateTime.now());
            }
            result.setStartTime(Date.from(v1Pod.getStatus().getStartTime().atZoneSameInstant(ZoneId.of("Asia/Shanghai")).toInstant()));
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("result set fail . result : {}  ", JSONUtils.toJSONString(result), e);
        }

        result.setUpdateTime(new Date());
        result.setSpec(spec);
        result.setName(engineName);
        result.setImage(image);
        result.setEndpoint(nodeService.getClusterExternalHost(), engineIdPart, String.valueOf(appPort));
        engineRepo.saveOrUpdate(userId, result);
        return result;
    }

    @Override
    public boolean release(String userId, String engineId) {
        if (!deployService.exist(namespace, engineId) && !svcService.exist(namespace, engineId)) {
            log.info("userId:{} has no deployment, no need to release engine:{}", userId, engineId);
            engineRepo.deleteByEngineId(engineId);
            return true;
        }

        if (!deployService.deleteDeployment(engineId, namespace)) {
            log.error("userId:{} release engine failed! reason: scale pod replicas 0 error! engineId:{}", userId, engineId);
            return false;
        }

        if (!svcService.exist(namespace, engineId)) {
            engineRepo.deleteByEngineId(engineId);
            return true;
        }

        if (!svcService.deleteSvc(engineId, namespace)) {
            log.warn("userId:{} release engine not completely! reason: delete svc error! engineId:{}", userId, engineId);
            return false;
        }

        if (!traefikService.deleteIngressRoute(engineId, namespace)) {
            log.warn("userId:{} release engine not completely! reason: delete ingress route error! engineId:{}", userId, engineId);
            return false;
        }

        engineRepo.deleteByEngineId(engineId);
        return true;
    }

    @Override
    public List<EngineDTO> listBy(String userId) {
        return engineRepo.findByUserId(userId);
    }
}
